#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>

#include "./include/common.h"

/**
 * @brief  gpio_export()
 * @note   导出 GPIO 引脚,相当于 echo X > /sys/class/gpio/export
 *         导出成功会生成 /sys/class/gpio/gpioX 目录
 * @param  [in] pins : gpio编号，传进来的是一个字符串
 * @param  [out]
 * @retval 
 */
int gpio_export(const char *pins)
{
    int fd = -1;
    size_t len = 0; // size_t  --> 32bit下通常为 unsigned int
    ssize_t ret = 0;// ssize_t --> 32bit下通常为 int
    char attr_file_path[32] = {0};

    if(pins == NULL)
    {
        PRTE("pins is NULL\n");
        return -1;
    }
    sprintf(attr_file_path, "%s", "/sys/class/gpio/export"); // 构建文件路径
    fd = open(attr_file_path, O_WRONLY);      // 打开 export 文件
    if (fd < 0)
    {
        PRTE("open %s error!\n", attr_file_path); // 打开文件失败
        return -2;
    }
    //PRT("open %s success!\n", attr_file_path);

    len = strlen(pins);         // 获取参数字符串的长度
    ret = write(fd, pins, len); // 将参数字符串写入文件， 导出 GPIO 引脚
    if (ret < 0)
    {
        PRTE("write %s error!\n", attr_file_path); // 写入文件失败
        return -3;
    }

    close(fd); // 关闭文件

    return 0;
}

/**
 * @brief  gpio_unexport()
 * @note   取消导出 GPIO 引脚,相当于 echo X > /sys/class/gpio/unexport
 *         取消导出成功会删除 /sys/class/gpio/gpioX 目录
 * @param  [in] pins : gpio编号，传进来的是一个字符串
 * @param  [out]
 * @retval 
 */
int gpio_unexport(const char *pins)
{
    int fd = -1;
    size_t len = 0;
    ssize_t ret = 0;
    char attr_file_path[32] = {0};

    if(pins == NULL)
    {
        PRTE("pins is NULL\n");
        return -1;
    }

    sprintf(attr_file_path, "%s", "/sys/class/gpio/unexport"); // 构建文件路径
    fd = open(attr_file_path, O_WRONLY); // 打开 unexport 文件
    if (fd < 0)
    {
        PRTE("open %s error!\n", attr_file_path); // 打开文件失败
        return -2;
    }
    //PRT("open %s success!\n", attr_file_path);

    len = strlen(pins);         // 获取参数字符串的长度
    ret = write(fd, pins, len); // 将参数字符串写入文件， 取消导出 GPIO 引脚
    if (ret < 0)
    {
        PRTE("write %s error!\n", attr_file_path); // 写入文件失败
        return -3;
    }

    close(fd); // 关闭文件

    return 0;
}

/**
 * @brief  gpio_ctrl()
 * @note   控制 GPIO 引脚的属性
 *         /sys/class/gpio/gpioX/direction  : 配置 GPIO 引脚为输入或输出模式。 
 *         /sys/class/gpio/gpioX/active_low : 配置 GPIO 极性
 *         /sys/class/gpio/gpioX/edge       : 控制中断的触发模式
 *         /sys/class/gpio/gpioX/value      : 设置高低电平/读取引脚高低电平状态
 * @param  [in] p_gpio_path : gpio路径，/sys/class/gpio/gpioX
 * @param  [in] p_attr_name : 属性文件名称，direction，active_low，edge，value
 * @param  [in] val         : 要写入的值 direction  - in/out ;
 *                                      active_low - 1/0 ;
 *                                      edge       - none/rising/falling/both ;
 *                                      value      - 1/0 ;
 * @param  [out]
 * @retval 
 */
int gpio_ctrl(char *p_gpio_path, const char *p_attr_name, const char *val)
{
    int fd = -1;
    size_t len = 0;
    ssize_t ret = 0;
    char attr_file_path[32] = {0};                 // 文件路径

    if(p_gpio_path == NULL || p_attr_name == NULL || val == NULL)
    {
        PRTE("p_gpio_path, p_attr_name or val is NULL\n");
        return -1;
    }

    sprintf(attr_file_path, "%s/%s", p_gpio_path, p_attr_name); // 构建文件路径， 格式为 p_gpio_path/p_attr_name 
    fd = open(attr_file_path, O_WRONLY);           // 打开文件
    if (fd < 0)
    {
        PRTE("open %s error!\n", attr_file_path); // 打开文件失败
        return -2;
    }
    //PRT("open %s success!\n", attr_file_path);

    len = strlen(val);         // 获取参数字符串的长度

    ret = write(fd, val, len); // 将参数字符串写入文件， 控制 GPIO 引脚的属性
    if (ret < 0)
    {
        PRTE("write %s error!\n", attr_file_path); // 写入文件失败
        return -3;
    }

    close(fd); // 关闭文件

    return 0;
}

/**
 * @brief  gpio_read_value()
 * @note   获取 GPIO 引脚的电平状态
 *         /sys/class/gpio/gpioX/value      : 设置高低电平/读取引脚高低电平状态
 * @param  [in] p_gpio_path : gpio路径，/sys/class/gpio/gpioX
 * @param  [in] p_attr_name : 属性文件名称，value
 * @param  [out]
 * @retval 
 */
int gpio_read_value(char *p_gpio_path, const char *p_attr_name)
{
    int fd = -1;
    ssize_t ret = 0;
    char attr_file_path[32] = {0};                 // 文件路径
    char buf[2] = {0}; // 缓冲区
    
    if(p_gpio_path == NULL || p_attr_name == NULL)
    {
        PRTE("p_gpio_path, p_attr_name is NULL\n");
        return -1;
    }

    sprintf(attr_file_path, "%s/%s", p_gpio_path, p_attr_name); // 构建文件路径， 格式为 "p_gpio_path/p_attr_name"
    fd = open(attr_file_path, O_RDONLY);              // 打开文件
    if (fd < 0)
    {
        PRTE("open %s error!\n", attr_file_path); // 打开文件失败
        return -1;
    }
	//PRT("open %s success!\n", attr_file_path);
    ret = read(fd, buf, 1); // 读取文件内容到缓冲区
    if(ret < 0)
    {
        PRTE("read from %s fail!\n", attr_file_path);
    }

    if (!strcmp(buf, "1"))
    {
        PRT("The value is high!\n"); // GPIO 引脚值为高电平
        return 1;
    }
    else if (!strcmp(buf, "0"))
    {
        PRT("The value is low!\n"); // GPIO 引脚值为低电平
        return 0;
    }
    
    close(fd); // 关闭文件

    return -1;
}

/**
 * @brief  gpio_interrupt()
 * @note   监听 GPIO 引脚的中断事件,中断发生时读取gpio的值
 *         /sys/class/gpio/gpioX/value      : 设置高低电平/读取引脚高低电平状态
 * @param  [in] p_gpio_path : gpio路径，/sys/class/gpio/gpioX
 * @param  [in] p_attr_name : 属性文件名称，value
 * @param  [out]
 * @retval 
 */
int gpio_interrupt(char *p_gpio_path, const char *p_attr_name)
{
    int fd = -1;
    ssize_t ret = 0;
    char attr_file_path[32] = {0}; // 文件路径
    struct pollfd fds[1];          // poll 结构体数组
    char buf[2] = {0};             // 缓冲区

    sprintf(attr_file_path, "%s/%s", p_gpio_path, p_attr_name); // 构建文件路径
    fd = open(attr_file_path, O_WRONLY);           // 打开文件
    if (fd < 0)
    {
        PRTE("open %s error!\n", attr_file_path); // 打开文件失败
        return -1;
    }
    //PRT("open %s success!\n", attr_file_path);

    memset((void *)fds, 0, sizeof(fds)); // 清空 poll 结构体数组
    fds[0].fd = fd;                      // 设置 poll 结构体的文件描述符
    fds[0].events = POLLPRI;             // 设置 poll 结构体的事件类型为 POLLPRI， 表示有紧急数据可读
    read(fd, buf, 2);                    // 读取文件内容， 清除中断事件
    while (1)
    {
        ret = poll(fds, 1, -1); // 调用 poll 函数等待中断事件发生， 阻塞直到事件发生
        if (ret <= 0)
        {
            PRTE("poll error \n"); // 调用 poll 失败或超时
            return -1;
        }
        // 不明白这里为什么poll会一直返回0xa，就是挂起状态，所以汇总段相当于一直发生，网上查阅资料暂时还没找到原因
        // 这个问题后面有时间再处理
        PRT("poll return!ret=%d,fds[0].revents=0x%x,fds[0].events=0x%x\n", ret, fds[0].revents, fds[0].events); // 输出中断事件的值
        if (fds[0].revents & POLLPRI)
        {
            lseek(fd, 0, SEEK_SET); // 重新定位文件指针到文件开头
            read(fd, buf, 2);       // 读取文件内容， 获取中断事件的值
            PRT("value from %s : buf[0]=%x buf[1]=%x\n", attr_file_path, buf[0], buf[1]); // 输出中断事件的值
        }
        sleep(1);
    }
    return 0;
}

int gpio_out_demo(int argc, const char *argv[]) // 主函数
{
    char gpio_path[32] = {0};                 // 文件路径

    sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]); // 构建 GPIO 路径， 格式为“/sys/class/gpio/gpio 引脚号”
    if (access(gpio_path, F_OK))                           // 检查 GPIO 路径是否存在
    {
        gpio_export(argv[1]); // 不存在则导出 GPIO 引脚
    }

    PRT("gpio_path:%s\n", gpio_path);
    gpio_ctrl(gpio_path, "direction", "out"); // 配置 GPIO 为输出模式
    gpio_ctrl(gpio_path, "value", argv[2]);   // 控制 GPIO 输出高低电平
    
    gpio_unexport(argv[1]);        // 最后取消导出 GPIO 引脚

    return 0;                      // 返回 0 表示程序正常退出
}

int gpio_in_demo(int argc, const char *argv[]) // 主函数
{
    int value = -1;
    char gpio_path[32] = {0};                              // 文件路径

    sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]); // 构建 GPIO 路径， 格式为"/sys/class/gpio/gpio 引脚号"
    if (access(gpio_path, F_OK))                           // 检查 GPIO 路径是否存在
    {
        gpio_export(argv[1]); // 不存在则导出 GPIO 引脚
    }

    PRT("gpio_path:%s\n", gpio_path);
    gpio_ctrl(gpio_path, "direction", "in");       // 配置 GPIO 为输入模式

    value = gpio_read_value(gpio_path, "value");   // 读取 GPIO 引脚的值

    PRT("The value is %d\n", value); // 打印读取的 GPIO 引脚的值
    gpio_unexport(argv[1]);             // 最后取消导出 GPIO 引脚

    return 0;                           // 返回 0 表示程序正常退出
}

int gpio_interrupt_demo(int argc, const char *argv[]) // 主函数
{
    char gpio_path[32] = {0}; // 文件路径

    sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]); // 构建 GPIO 路径
    if (access(gpio_path, F_OK))                           // 检查 GPIO 路径是否存在
    {
        gpio_export(argv[1]); // 不存在则导出 GPIO 引脚
    }

    PRT("gpio_path:%s\n", gpio_path);
    gpio_ctrl(gpio_path, "direction", "in"); // 设置 GPIO 引脚为输入模式
    gpio_ctrl(gpio_path, "edge", "rising");    // 设置 GPIO 引脚的中断触发方式为上升沿
    gpio_interrupt(gpio_path, "value");      // 监听 GPIO 引脚的中断事件

    gpio_unexport(argv[1]); // 最后取消导出 GPIO 引脚

    return 0;               // 返回 0 表示程序正常退出
}

int main(int argc, const char *argv[])
{
    /* code */
    // gpio_out_demo(argc, argv); // ./app_demo.out 3 1/0
    // gpio_in_demo(argc, argv); // ./app_demo.out 18
    gpio_interrupt_demo(argc, argv); //./app_demo.out 18 &
    return 0;
}